<?php
/*
License:
	MIT-style license

Author:
	Jan Kassens

Note:
	magic_quotes_gpc needs to be turned off.
*/
class MooRemote{

	private $functions = array();
	private $classes = array();

	private $options;

	public function __construct($options = array()){
		$this->options = $options;
	}

	public function getJavascript($tag = true){
		$functions = $this->functions;
		foreach($this->classes as $class => $methods){
			unset($methods['%object']);
			$functions[] = array('class' => $class, 'methods' => $methods);
		}
		$return = "var mooRemote=new MooRemote(" . json_encode($functions);
		if(count($this->options) > 0) $return .= "," . json_encode($this->options);
		$return .= ");";
		if($tag) $return = "<script type=\"text/javascript\">$return</script>";
		return $return;
	}

	public function processRequest($exit = true){
		if(!isset($_SERVER['HTTP_X_REQUESTED_WITH']) || $_SERVER['HTTP_X_REQUESTED_WITH'] != 'MooRequest') return;
		set_error_handler(array($this, 'error'), E_ALL);
		try {
			$call = json_decode($_POST['call'], true);
			$method = $call['method'];
			$params = $call['params'];
			$callback = null;
			if (is_string($method)){ // function
				if(!in_array($method, $this->functions)) throw new Exception("Function $function is not registered.");
				$callback = $method;
			} else { // method
				$class = $method[0];
				$method = $method[1];
				if(!isset($this->classes[$class])) throw new Exception("Class $class is not registered.");
				if(!in_array($method, $this->classes[$class])) throw new Exception("Method $class::$method is not registered.");
				$callback = array(isset($this->classes[$class]['%object']) ? $this->classes[$class]['%object'] : $class, $method);
			}
			if(!is_callable($callback, false, $callable)) throw new Exception("Method is not callable.");
			ob_start();
			$result = call_user_func_array($callback, $params);
			$js = ob_get_contents();
			ob_end_clean();
			$this->response(array('result' => $result, 'script' => $js));
		} catch (Exception $e) {
			$this->error(1, $e->getMessage());
		}
		restore_error_handler();
		if($exit) exit;
	}

	public function registerFunction($callback){
		$this->functions[] = $callback;
	}
	
	/**
	 * Registers a Class or an Object to be callable from JS
	 * 
	 * @param string Name to be used in JS, also name of the PHP class in case you dont pass an Object as second parameter.
	 * @param string optional, Object instance to be called from JS.
	 * @param array optional, array of Strings representing the methods callable from JS. Defaults to every
	 *              public (and in case you didn't pass an object also static) method.
	 */
	public function registerClass($name, $object = null, $methods = null){
		$isObj = is_object($object);
		if (!$isObj) $methods = $object;
		if (!$methods) {
			$class = new ReflectionClass($isObj ? get_class($object) : $name);
			$methods = array();
			$mm = $class->getMethods();
			foreach($mm as $m){
				if ($m->isPublic() && ($isObj || $m->isStatic()) && !$m->isConstructor() && !$m->isDestructor()) $methods[] = $m->getName();
			}
		}
		if ($isObj) $methods['%object'] = $object;
		$this->classes[$name] = $methods;
	}
	
	private function error($errno, $errstr){
		$this->response(array('error' => $errstr));
		exit;
	}
	
	private function getMethods($class){
		$isObj = is_object($class);
		$class = new ReflectionClass($isObj ? get_class($class) : $class);
		$return = array();
		$methods = $class->getMethods();
		foreach($methods as $m){
			if ($m->isPublic() && ($isObj || $m->isStatic())) $return[] = $m->getName();
		}
		return $return;
	}

	// data: result, script, error;
	private function response($data){
		if(empty($data['script'])) unset($data['script']);
		echo json_encode($data);
	}

}
?>